rework subrip format synchronization. (#536)
authortsteven4 <13596209+tsteven4@users.noreply.github.com>
Mon, 13 Apr 2020 14:36:32 +0000 (08:36 -0600)
committerGitHub <noreply@github.com>
Mon, 13 Apr 2020 14:36:32 +0000 (08:36 -0600)
* rework subrip format synchronization.

support subsecond synchronization.
validate option input.
add debug messages for synchronization.
correct option text for GUI.

* update serialization reference files for corrected subrip help.

reference/format3.txt
reference/help.txt
reference/track/gpx_subsecond-sample-shifted~subrip.srt [new file with mode: 0644]
reference/track/gpx_subsecond-sample~subrip.srt
subrip.cc
testo.d/subrip.test

index 6aa10f669fa8a3ff815923c53acf4d1d61121688..ca3501c71aa19905e84f41a51586ed4aaa2f0b9a 100644 (file)
@@ -1272,11 +1272,11 @@ option  skytraq-bin     gps-week-rollover       GPS week rollover period we're in (-1: best
 
 file   ---w--  subrip  srt     SubRip subtitles for video mapping (.srt)       subrip
        https://www.gpsbabel.org/htmldoc-1.6.0/fmt_subrip.html
-option subrip  video_time      Video position for which exact GPS time is known (hhmmss, default is 0:00:00)   string                          https://www.gpsbabel.org/htmldoc-1.6.0/fmt_subrip.html#fmt_subrip_o_video_time
+option subrip  video_time      Video position for which exact GPS time is known (hhmmss[.sss], default is 00:00:00,000)        string                          https://www.gpsbabel.org/htmldoc-1.6.0/fmt_subrip.html#fmt_subrip_o_video_time
 
-option subrip  gps_time        GPS time at position video_time (hhmmss, default is first timestamp of track)   string                          https://www.gpsbabel.org/htmldoc-1.6.0/fmt_subrip.html#fmt_subrip_o_gps_time
+option subrip  gps_time        GPS time at position video_time (hhmmss[.sss], default is first timestamp of track)     string                          https://www.gpsbabel.org/htmldoc-1.6.0/fmt_subrip.html#fmt_subrip_o_gps_time
 
-option subrip  gps_date        GPS date at position video_time (hhmmss, default is first timestamp of track)   string                          https://www.gpsbabel.org/htmldoc-1.6.0/fmt_subrip.html#fmt_subrip_o_gps_date
+option subrip  gps_date        GPS date at position video_time (yyyymmdd, default is first timestamp of track) string                          https://www.gpsbabel.org/htmldoc-1.6.0/fmt_subrip.html#fmt_subrip_o_gps_date
 
 option subrip  format  Format for subtitles    string  %s km/h %e m\n%t %l                     https://www.gpsbabel.org/htmldoc-1.6.0/fmt_subrip.html#fmt_subrip_o_format
 
index 2429452d6ecf4352f2bc1462b5bc4d596760e6a1..edd3bb9ddf37e27231f0c70983463e3170e0a3a7 100644 (file)
@@ -641,8 +641,8 @@ File Types (-i and -o options):
          gps-week-rollover     GPS week rollover period we're in (-1: best guess) 
        subrip                SubRip subtitles for video mapping (.srt)
          video_time            Video position for which exact GPS time is known ( 
-         gps_time              GPS time at position video_time (hhmmss, default i 
-         gps_date              GPS date at position video_time (hhmmss, default i 
+         gps_time              GPS time at position video_time (hhmmss[.sss], def 
+         gps_date              GPS date at position video_time (yyyymmdd, default 
          format                Format for subtitles 
        stmsdf                Suunto Trek Manager (STM) .sdf files
          index                 Index of route (if more than one in source) 
diff --git a/reference/track/gpx_subsecond-sample-shifted~subrip.srt b/reference/track/gpx_subsecond-sample-shifted~subrip.srt
new file mode 100644 (file)
index 0000000..9283eeb
--- /dev/null
@@ -0,0 +1,20 @@
+1
+00:00:00,000 --> 00:00:00,200
+50.6 km/h  289 m
+17:47:25 Lat=49.79471 Lon=9.83400
+
+2
+00:00:00,200 --> 00:00:00,400
+51.0 km/h  289 m
+17:47:25 Lat=49.79473 Lon=9.83398
+
+3
+00:00:00,400 --> 00:00:00,600
+50.7 km/h  289 m
+17:47:25 Lat=49.79476 Lon=9.83395
+
+4
+00:00:00,600 --> 00:00:00,800
+51.1 km/h  290 m
+17:47:26 Lat=49.79478 Lon=9.83393
+
index 00803a8abf46c0e59b69bad44c0700cdace3d7a0..d74d7837a2493697e1ac818495d65f54b153e0bb 100644 (file)
@@ -1,25 +1,25 @@
 1
-00:00:00,200 --> 00:00:00,400
+00:00:00,000 --> 00:00:00,200
 49.7 km/h  289 m
 17:47:25 Lat=49.79469 Lon=9.83402
 
 2
-00:00:00,400 --> 00:00:00,600
+00:00:00,200 --> 00:00:00,400
 50.6 km/h  289 m
-17:47:25 Lat=49.79472 Lon=9.83400
+17:47:25 Lat=49.79471 Lon=9.83400
 
 3
-00:00:00,600 --> 00:00:00,800
+00:00:00,400 --> 00:00:00,600
 51.0 km/h  289 m
-17:47:25 Lat=49.79474 Lon=9.83398
+17:47:25 Lat=49.79473 Lon=9.83398
 
 4
-00:00:00,800 --> 00:00:01,000
+00:00:00,600 --> 00:00:00,800
 50.7 km/h  289 m
-17:47:25 Lat=49.79476 Lon=9.83396
+17:47:25 Lat=49.79476 Lon=9.83395
 
 5
-00:00:01,000 --> 00:00:02,000
-51.1 km/h  289 m
-17:47:26 Lat=49.79479 Lon=9.83394
+00:00:00,800 --> 00:00:01,000
+51.1 km/h  290 m
+17:47:26 Lat=49.79478 Lon=9.83393
 
index 9d2ea3c34c970a320de0b3c6bf41fd6d942bf55b..8db5d1568071a71abc825ded68b8e1e9856eb329 100644 (file)
--- a/subrip.cc
+++ b/subrip.cc
     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
  */
+#include <QtCore/QDate>         // for QDate
+#include <QtCore/QDateTime>     // for QDateTime, operator<<
+#include <QtCore/QDebug>        // for QDebug
+#include <QtCore/QString>       // for QString
+#include <QtCore/QTime>         // for QTime
+#include <QtCore/QVector>       // for QVector
+#include <QtCore/Qt>            // for UTC
 
 #include "defs.h"
-#include <cstdio> /* for gmtime */
+#include "gbfile.h"             // for gbfprintf, gbfclose, gbfopen, gbfwrite, gbfile
+#include "src/core/datetime.h"  // for DateTime
+#include "src/core/logging.h"   // for Fatal
+
 
 #define MYNAME "subrip"
 
@@ -29,7 +39,9 @@ static char* opt_videotime;
 static char* opt_gpstime;
 static char* opt_gpsdate;
 static char* opt_format;
-static time_t time_offset;
+static QDateTime gps_datetime;    // Date time corresponding to video video_offset_ms
+static QDateTime video_datetime;  // Date time corresponding to video time 00:00:00,000.
+static int video_offset_ms;
 static int stnum;
 static gbfile* fout;
 static const Waypoint* prevwpp;
@@ -38,27 +50,16 @@ static double gradient;
 
 /* internal helper functions */
 
-static time_t
-sync_time(time_t arg_gpstime, char* arg_videotime)
+static QTime
+video_time(const QDateTime& dt)
 {
-  static time_t videotime_t;
-  static struct tm* ptm_video;
-  static time_t result;
-
-  videotime_t = 0;
-  ptm_video = gmtime(&videotime_t);
-  if (arg_videotime) {
-    sscanf(arg_videotime, "%2d%2d%2d", &ptm_video->tm_hour, &ptm_video->tm_min, &ptm_video->tm_sec);
-  }
-  videotime_t = mkgmtime(ptm_video);
-  result = (arg_gpstime - videotime_t);
-  return result;
+  return QTime::fromMSecsSinceStartOfDay(video_datetime.msecsTo(dt));
 }
 
 static void
 subrip_prevwp_pr(const Waypoint* waypointp)
 {
-  QDateTime enddtime;
+  static long long deltaoffset;
 
   /* Now that we have the next waypoint, we can write out the subtitle for
    * the previous one.
@@ -67,74 +68,80 @@ subrip_prevwp_pr(const Waypoint* waypointp)
   /* If this condition is not true, the waypoint is before the beginning of
    * the video and will be ignored
    */
-  if (prevwpp->GetCreationTime().toTime_t() < time_offset) {
+  if (prevwpp->GetCreationTime() < video_datetime) {
     return;
   }
 
   gbfprintf(fout, "%d\n", stnum++);
 
   /* Writes start and end time for subtitle display to file. */
-  QDateTime startdtime = prevwpp->GetCreationTime().addSecs(-time_offset);
+  QDateTime end_datetime;
   if (!waypointp) {
-    enddtime = startdtime.addSecs(1);
+    // prevwpp is the last waypoint, so we don't have a datetime for the
+    // next waypoint.  Instead, estimate it from length of the previous
+    // video frame.
+    end_datetime = prevwpp->GetCreationTime().addMSecs(deltaoffset);
   } else {
-    enddtime = waypointp->GetCreationTime().addSecs(-time_offset);
+    end_datetime = waypointp->GetCreationTime();
+    deltaoffset = prevwpp->GetCreationTime().msecsTo(waypointp->GetCreationTime());
   }
-  QTime starttime = startdtime.toUTC().time();
-  QTime endtime = enddtime.toUTC().time();
+  QTime starttime = video_time(prevwpp->GetCreationTime());
+  QTime endtime = video_time(end_datetime);
   gbfprintf(fout, "%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d\n",
-    starttime.hour(), starttime.minute(), starttime.second(), starttime.msec(),
-    endtime.hour(), endtime.minute(), endtime.second(), endtime.msec());
+            starttime.hour(), starttime.minute(), starttime.second(), starttime.msec(),
+            endtime.hour(), endtime.minute(), endtime.second(), endtime.msec());
 
   for (char* c = opt_format; *c != '\0' ; c++) {
     char fmt;
 
     switch (*c) {
     case '%':
-      fmt = *++c;   
+      fmt = *++c;
       is_fatal(fmt == '\0', "No character after %% in subrip format");
-      
+
       switch (fmt) {
       case 's':
-        if WAYPT_HAS(prevwpp, speed)
-           gbfprintf(fout, "%2.1f", MPS_TO_KPH(prevwpp->speed));
-        else  
-           gbfprintf(fout, "--.-");
+        if WAYPT_HAS(prevwpp, speed) {
+          gbfprintf(fout, "%2.1f", MPS_TO_KPH(prevwpp->speed));
+        } else {
+          gbfprintf(fout, "--.-");
+        }
         break;
       case 'e':
-        if (prevwpp->altitude != unknown_alt)  
-          gbfprintf(fout, "%4d", (int)prevwpp->altitude);
-        else
+        if (prevwpp->altitude != unknown_alt) {
+          gbfprintf(fout, "%4.0f", prevwpp->altitude);
+        } else {
           gbfprintf(fout, "   -");
+        }
         break;
       case 'v':
         gbfprintf(fout, "%2.2f", vspeed);
         break;
       case 'g':
-          gbfprintf(fout, "%2.1f%%", gradient);
-          break;
-      case 't':
-        {
-          QTime t = prevwpp->GetCreationTime().toUTC().time();
-          gbfprintf(fout, "%02d:%02d:%02d", t.hour(), t.minute(), t.second());
-          break;
-        }
+        gbfprintf(fout, "%2.1f%%", gradient);
+        break;
+      case 't': {
+        QTime t = prevwpp->GetCreationTime().toUTC().time();
+        gbfprintf(fout, "%02d:%02d:%02d", t.hour(), t.minute(), t.second());
+        break;
+      }
       case 'l':
-        // The +.00005 is for rounding.
         gbfprintf(fout, "Lat=%0.5lf Lon=%0.5lf",
-          prevwpp->latitude+.000005, prevwpp->longitude+.000005);
+                  prevwpp->latitude, prevwpp->longitude);
         break;
       case 'c':
-        if (prevwpp->cadence != 0)
+        if (prevwpp->cadence != 0) {
           gbfprintf(fout, "%3u", prevwpp->cadence);
-        else
+        } else {
           gbfprintf(fout, "  -");
+        }
         break;
       case 'h':
-        if (prevwpp->heartrate != 0)
+        if (prevwpp->heartrate != 0) {
           gbfprintf(fout, "%3u", prevwpp->heartrate);
-        else
+        } else {
           gbfprintf(fout, "  -");
+        }
         break;
       }
 
@@ -171,16 +178,22 @@ subrip_trkpt_pr(const Waypoint* waypointp)
    * but also pre-previous, so we calculate vspeed right before forgetting
    * the previous.
    */
-  if ((stnum == 1) && (time_offset == 0))
-    /*
-     * esoteric bug: GPS tracks created on Jan 1, 1970 at midnight would cause
-     * undesirable behavior here. But if you run into this problem, I assume
-     * you are capable of time-travel as well as inventing a high-tech system
-     * some 20 years before the rest of mankind does, so finding a prettier
-     * way of solving this should be trivial to you :-)
-     */
-  {
-    time_offset = sync_time(waypointp->GetCreationTime().toTime_t(), opt_videotime);
+  if (!video_datetime.isValid()) {
+    if (!gps_datetime.isValid()) {
+      // If gps_date and gps_time options weren't used, then we use the
+      // datetime of the first waypoint to sync to the video.
+      gps_datetime = waypointp->GetCreationTime().toUTC();
+    }
+    video_datetime = gps_datetime.addMSecs(-video_offset_ms).toUTC();
+    if (global_opts.debug_level >= 2) {
+      qDebug().noquote() << "GPS track start is           "
+                         << waypointp->GetCreationTime().toUTC().toString(Qt::ISODateWithMs);
+      qDebug().noquote() << "Synchronizing"
+                         << video_time(gps_datetime).toString("HH:mm:ss,zzz")
+                         << "to" << gps_datetime.toString(Qt::ISODateWithMs);
+      qDebug().noquote() << "Video start   00:00:00,000 is"
+                         << video_datetime.toString(Qt::ISODateWithMs);
+    }
   }
 
   if (prevwpp) {
@@ -196,46 +209,45 @@ subrip_trkpt_pr(const Waypoint* waypointp)
 static void
 subrip_wr_init(const QString& fname)
 {
-  time_t gpstime_t;
-
   stnum = 1;
-
-  time_offset = 0;
-
   prevwpp = nullptr;
   vspeed = 0;
   gradient = 0;
 
+  if ((opt_gpstime == nullptr) != (opt_gpsdate == nullptr)) {
+    Fatal() << MYNAME ": Either both or neither of the gps_date and gps_time options must be supplied!";
+  }
+  gps_datetime = QDateTime();
   if ((opt_gpstime != nullptr) && (opt_gpsdate != nullptr)) {
-    time(&gpstime_t);
-    struct tm* ptm_gps = gmtime(&gpstime_t);
-    if (opt_gpstime) {
-      sscanf(opt_gpstime, "%2d%2d%2d", &ptm_gps->tm_hour, &ptm_gps->tm_min, &ptm_gps->tm_sec);
+    QDate gps_date = QDate::fromString(opt_gpsdate, "yyyyMMdd");
+    if (!gps_date.isValid()) {
+      Fatal().nospace() << MYNAME ": option gps_date value (" << opt_gpsdate << ") is invalid.  Expected yyyymmdd.";
     }
-    if (opt_gpsdate) {
-      sscanf(opt_gpsdate, "%4d%2d%2d", &ptm_gps->tm_year, &ptm_gps->tm_mon, &ptm_gps->tm_mday);
-      /*
-       * Don't ask me why we need to do this nonsense, but it seems to be necessary:
-       * Years are two-digit since this was fashionable in the mid-1900s.
-       * For dates after 2000, just add 100 to the year.
-       * Months are zero-based (0 is January), but days are one-based.
-       * Makes sense, eh?
-       * Btw: correct dates will result in incorrect timestamps and you'll
-       * never figure out why. Suppose that's to confuse the Russians,
-       * given that the system was developed during the Cold War. But that
-       * is true for most of Unix.
-       * Make a difference - contribute to ReactOS.
-       */
-      ptm_gps->tm_year-=1900;
-      ptm_gps->tm_mon--;
+    QTime gps_time = QTime::fromString(opt_gpstime, "HHmmss");
+    if (!gps_time.isValid()) {
+      gps_time = QTime::fromString(opt_gpstime, "HHmmss.z");
+      if (!gps_time.isValid()) {
+        Fatal().nospace() << MYNAME ": option gps_time value (" << opt_gpstime << ") is invalid.  Expected hhmmss[.sss]";
+      }
     }
-    gpstime_t = mkgmtime(ptm_gps);
-    time_offset = sync_time(gpstime_t, opt_videotime);
+    gps_datetime = QDateTime(gps_date, gps_time, Qt::UTC);
+  }
 
+  video_offset_ms = 0;
+  if (opt_videotime != nullptr) {
+    QTime video_time = QTime::fromString(opt_videotime, "HHmmss");
+    if (!video_time.isValid()) {
+      video_time = QTime::fromString(opt_videotime, "HHmmss.z");
+      if (!video_time.isValid()) {
+        Fatal().nospace() << MYNAME ": option video_time value (" << opt_videotime << ") is invalid.  Expected hhmmss[.sss].";
+      }
+    }
+    video_offset_ms = video_time.msecsSinceStartOfDay();
   }
 
-  fout = gbfopen(fname, "wb", MYNAME);
+  video_datetime = QDateTime();
 
+  fout = gbfopen(fname, "wb", MYNAME);
 }
 
 static void
@@ -261,10 +273,9 @@ subrip_write()
 /* arguments: definitions of format-specific arguments */
 
 static QVector<arglist_t> subrip_args = {
-  // FIXME: document that gps_date and gps_time must be specified together or they will both be ignored and the timestamp of the first trackpoint will be used.
-  {"video_time", &opt_videotime, "Video position for which exact GPS time is known (hhmmss, default is 0:00:00)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr },
-  {"gps_time", &opt_gpstime, "GPS time at position video_time (hhmmss, default is first timestamp of track)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr },
-  {"gps_date", &opt_gpsdate, "GPS date at position video_time (hhmmss, default is first timestamp of track)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr },
+  {"video_time", &opt_videotime, "Video position for which exact GPS time is known (hhmmss[.sss], default is 00:00:00,000)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr },
+  {"gps_time", &opt_gpstime, "GPS time at position video_time (hhmmss[.sss], default is first timestamp of track)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr },
+  {"gps_date", &opt_gpsdate, "GPS date at position video_time (yyyymmdd, default is first timestamp of track)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr },
   {"format", &opt_format, "Format for subtitles", "%s km/h %e m\\n%t %l", ARGTYPE_STRING, ARG_NOMINMAX, nullptr },
 };
 
index db19fc073e99370219f6fa5ce753509c2da27ae1..48e1bdc723ce188c5f8d4c5303346ccc19cc7f36 100644 (file)
@@ -1,4 +1,11 @@
 rm -f ${TMPDIR}/subrip.srt
 gpsbabel -i gpx -f ${REFERENCE}/track/gpx_subsecond-sample.gpx -o subrip -F ${TMPDIR}/subrip.srt
-# FIXME: This can't work right until we move to "real" subsecond support.
 compare  ${REFERENCE}/track/gpx_subsecond-sample~subrip.srt ${TMPDIR}/subrip.srt
+gpsbabel -i gpx -f ${REFERENCE}/track/gpx_subsecond-sample.gpx -o subrip,gps_date=20110702,gps_time=174725.200 -F ${TMPDIR}/subrip.srt
+compare  ${REFERENCE}/track/gpx_subsecond-sample~subrip.srt ${TMPDIR}/subrip.srt
+gpsbabel -i gpx -f ${REFERENCE}/track/gpx_subsecond-sample.gpx -o subrip,video_time=000000,gps_date=20110702,gps_time=174725.200 -F ${TMPDIR}/subrip.srt
+compare  ${REFERENCE}/track/gpx_subsecond-sample~subrip.srt ${TMPDIR}/subrip.srt
+gpsbabel -i gpx -f ${REFERENCE}/track/gpx_subsecond-sample.gpx -o subrip,video_time=000000.200,gps_date=20110702,gps_time=174725.400 -F ${TMPDIR}/subrip.srt
+compare  ${REFERENCE}/track/gpx_subsecond-sample~subrip.srt ${TMPDIR}/subrip.srt
+gpsbabel -i gpx -f ${REFERENCE}/track/gpx_subsecond-sample.gpx -o subrip,gps_date=20110702,gps_time=174725.400 -F ${TMPDIR}/subrip-shifted.srt
+compare  ${REFERENCE}/track/gpx_subsecond-sample-shifted~subrip.srt ${TMPDIR}/subrip-shifted.srt